﻿#define SPLASHSCREEN_ACTIVE

using System;
using OpenGLES;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
using Microsoft.Win32;
using System.Xml;
using System.Reflection;
using System.Collections;

namespace PFD
{
    public enum OperationalMode
    {
        Simulated,
        RealWorld
    };

    public enum ScreenOrientation
    {
        Landscape,
        Portrait
    };


    public partial class MainForm : Form, IExtensionFactory
    {

        [DllImport("coredll")]
        extern static IntPtr GetDC(IntPtr hwnd);

        [DllImport("coredll")]
        extern static void SystemIdleTimerReset();


        IRenderDevice renderer;

        #region FPS CALCULATION
        int[] myFrameTracker = new int[20];
        int myLastFrame = 0;
        float myFps = 0;
        float fDelta = 0;
        #endregion FPS CALCULATION




        #region USER PROPERTIES
        bool bShowFPS = false;
        static OperationalMode operationalMode = OperationalMode.Simulated;
        #endregion USER PROPERTIES

        /// <summary>
        /// poolContainer stores all global pool resources. The pool resources
        /// are specified in the XML file.
        /// </summary>
        public GlobalPoolContainer     poolContainer;

        /// <summary>
        /// strExecutionPath stores the path of the executable
        /// </summary>
        string strExecutionPath;

        /// <summary>
        /// System Variables Storage. Maintains all references to the system
        /// Variable Connectors (not the real variables)
        /// </summary>
        public static SystemVariables sysVars = new SystemVariables();

        /// <summary>
        /// List of 3D world objects.
        /// </summary>
        List<WorldObject> blist = new List<WorldObject>();

        /// <summary>
        /// List of extension components
        /// </summary>
        List<IExtensionComponent> extDlls = new List<IExtensionComponent>();

        #region System Variable Container Objects
        /// <summary>
        /// GpsSystem is the GPS System Variable Container
        /// Currently contains Airspeed, Heading and all other
        /// Gps maintained system variables
        /// </summary>
        public GpsSystem gps = new GpsSystem(operationalMode);
        /// <summary>
        /// GSensor is the HTC G Sensor Variable Container
        /// Currently contains PitchAngle and RollAngle
        /// </summary>
        public GSensorSystem gsensor = new GSensorSystem(operationalMode);
        /// <summary>
        /// Compass is the HTC Compass sensor Variable Container
        /// Currently contains only Heading
        /// </summary>
        public CompassSystem compass = new CompassSystem(operationalMode);
        /// <summary>
        /// sysvars contains general system maintained variables
        /// like orientation (portrait/landscape) and other things
        /// </summary>
        public  GeneralVarsSystem sysvars = new GeneralVarsSystem();
        /// <summary>
        /// apvars contains all autopilot simulation variables
        /// </summary>
        public AutopilotSystem apvars = new AutopilotSystem();
        #endregion
        SplashScreen sc;

        internal RemoteDataProvider udpProvider;

        public MainForm()
        {
            bool bDX = false;
            if ((int)RegistryClass.GetValue("Software\\LSD\\PFD", "UseDirectX", 0) == 1)
            {
                renderer = new RenderDeviceX();
                bDX = true;
            }
            else
            {
                renderer = new RenderDeviceOGL();
            }

#if SPLASHSCREEN_ACTIVE
            sc =  new SplashScreen();
            sc.Show();
            sc.Invalidate();
            sc.Update();
#endif
            //  Range :
            //  0 = initialize Component
            //  1 = Initialize OpenGL
            //  2 = Loading Vector Resources
            //  3 = Loading Bitmaps
            //  4 = Load Objects
            //  5 = Initialize Internal Variables
#if SPLASHSCREEN_ACTIVE
            sc.setProgressRange(0, 5, new string[6] {"Initialize Component", "Initialize Graphics", "Loading Vector Resources", "Loading Bitmap Resources", "Loading Objects", "Initializing internal variables"});
#endif
            strExecutionPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);

            //  Initialize form and menu
            InitializeComponent();
#if SPLASHSCREEN_ACTIVE
            sc.nextStep();
#endif
            //  perform graphics renderer initialization
            renderer.Initialize(this);
#if SPLASHSCREEN_ACTIVE
            sc.nextStep();
#endif
            //  Load external data from XML
            LoadData();
#if SPLASHSCREEN_ACTIVE
            sc.nextStep();
#endif

            //  Check for interval when to make the keepalive calls to power management
            DetermineSystemIdleTimeout();

            //  Register Variables
            sysVars.objVarsFloat.Add(gsensor.rollAngle.Name, gsensor.rollAngle);
            sysVars.objVarsFloat.Add(gsensor.pitchAngle.Name, gsensor.pitchAngle);
            sysVars.objVarsFloat.Add(gps.course.Name, gps.course);
            sysVars.objVarsFloat.Add(gps.altitude.Name, gps.altitude);
            sysVars.objVarsFloat.Add(gps.speed.Name, gps.speed);
            sysVars.objVarsFloat.Add(sysvars.LandscapeOrientation.Name, sysvars.LandscapeOrientation);
            sysVars.objVarsFloat.Add(sysvars.PortraitOrientation.Name, sysvars.PortraitOrientation);
            sysVars.objVarsBool.Add(apvars.AirspeedLock.Name, apvars.AirspeedLock);
            sysVars.objVarsFloat.Add(apvars.AirspeedValue.Name, apvars.AirspeedValue);
            sysVars.objVarsBool.Add(apvars.AltitudeLock.Name, apvars.AltitudeLock);
            sysVars.objVarsFloat.Add(apvars.AltitudeValue.Name, apvars.AltitudeValue);
            sysVars.objVarsBool.Add(apvars.ApproachLock.Name, apvars.ApproachLock);
            sysVars.objVarsBool.Add(apvars.AutoThrottleEngage.Name, apvars.AutoThrottleEngage);
            sysVars.objVarsBool.Add(apvars.FlightDirectorEngage.Name, apvars.FlightDirectorEngage);
            sysVars.objVarsBool.Add(apvars.GlideslopeLock.Name, apvars.GlideslopeLock);
            sysVars.objVarsBool.Add(apvars.HeadingLock.Name, apvars.HeadingLock);
            sysVars.objVarsFloat.Add(apvars.HeadingValue.Name, apvars.HeadingValue);
            sysVars.objVarsBool.Add(apvars.MasterEngage.Name, apvars.MasterEngage);
            sysVars.objVarsBool.Add(apvars.Nav1Lock.Name, apvars.Nav1Lock);

            //  Register the update callback callbacks with each individual variable
            gsensor.pitchAngle.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            gsensor.rollAngle.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            gps.course.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            gps.altitude.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            gps.speed.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            compass.Heading.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            sysvars.LandscapeOrientation.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            sysvars.PortraitOrientation.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            apvars.AirspeedLock.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);
            apvars.AirspeedValue.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            apvars.AltitudeLock.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);
            apvars.AltitudeValue.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            apvars.ApproachLock.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);
            apvars.AutoThrottleEngage.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);
            apvars.FlightDirectorEngage.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);
            apvars.GlideslopeLock.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);
            apvars.HeadingLock.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);
            apvars.HeadingValue.OnChange += new EventHandler<SysVarChangeEventArgs<float>>(sysVar_OnChange);
            apvars.MasterEngage.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);
            apvars.Nav1Lock.OnChange += new EventHandler<SysVarChangeEventArgs<bool>>(sysVar_OnChange);

            // Set the checkmarks for menus to defaults
            this.menuModeReal.Checked = (operationalMode == OperationalMode.RealWorld);
            this.menuModeSimulated.Checked = (operationalMode == OperationalMode.Simulated);

#if SPLASHSCREEN_ACTIVE
            //  Quit the splash screen
            sc.Close();
#endif

            //  Trigger the variables to get an initial update of the display
            sysvars.Trigger();
            gsensor.Trigger();
            gps.Trigger();
            compass.Trigger();
            apvars.Trigger();

            //  create the remote data receiver and start the threads in simulated mode
            udpProvider = new RemoteDataProvider(this);
            if (operationalMode == OperationalMode.Simulated)
                udpProvider.Run();

            if (bDX)
            {
                menuModeUseDirectX.Checked = true;
                menuModeUseOpenGL.Checked = false;
            }
            else
            {
                menuModeUseOpenGL.Checked = true;
                menuModeUseDirectX.Checked = false;
            }
        }

        void sysVar_OnChange(object sender, SysVarChangeEventArgs<float> e)
        {
            if (sysVars.floatVars.ContainsKey(e.Name))
            {
                foreach (ISystemVarConnector<float> client in sysVars.floatVars[e.Name])
                {
                    client.DoUpdate(e.Value);
                }
            }
        }

        void sysVar_OnChange(object sender, SysVarChangeEventArgs<bool> e)
        {
            if (sysVars.boolVars.ContainsKey(e.Name))
            {
                foreach (ISystemVarConnector<bool> client in sysVars.boolVars[e.Name])
                {
                    client.DoUpdate(e.Value);
                }
            }
        }


        private void LoadData()
        {
            XmlDocument xdoc = new XmlDocument();
            xdoc.Load(strExecutionPath + "\\PanelDef.xml");

#if SPLASHSCREEN_ACTIVE
            sc.nextStep();
#endif

            //  Now load all the textures
            XmlNodeList texpool = xdoc.SelectNodes("/PFDDEF/RESOURCES/BITMAPS/BITMAP");
            if (texpool != null)
            {
                poolContainer.texturePool = TexturePoolReader.LoadFromXML(strExecutionPath, texpool, renderer);
            }
#if SPLASHSCREEN_ACTIVE
            sc.nextStep();
#endif

            //  Now load the panels
            XmlNodeList panels = xdoc.SelectNodes("/PFDDEF/OBJECTS/OBJECT");
            foreach (XmlNode panel in panels)
            {
                WorldObject b;
                b = new WorldObject();
                b.LoadFromXML(panel, poolContainer, renderer);

                blist.Add(b);
            }

            //  Finally load the extension DLLs
            XmlNode n1 = xdoc.SelectSingleNode("/PFDDEF");
            XmlNode n2 = xdoc.SelectSingleNode("/PFDDEF/EXTENSIONS");
            XmlNode n3 = n2.SelectSingleNode("DLL");
            XmlNodeList dlls = n2.SelectNodes("DLL");
            try
            {
                foreach (XmlNode dll in dlls)
                {
                    string szName = dll.SelectSingleNode("NAME").InnerText;
                    string szPath = dll.SelectSingleNode("RELATIVEPATH").InnerText;
                    //  load the dll at runtime

                    System.Reflection.Assembly extDll;
                    extDll = System.Reflection.Assembly.LoadFrom(szPath);
                    foreach (Type t in extDll.GetTypes())
                    {
                        if (t.IsClass == true)
                        {
                            Type[] interfaces = t.GetInterfaces();
                            if (((IList)interfaces).Contains(typeof(IExtensionComponent)))
                            {
                                object obj = Activator.CreateInstance(t);
                                IExtensionComponent iext = obj as IExtensionComponent;
                                extDlls.Add(iext);
                                iext.Initialize(this);
                                //                            break;
                            }
                            /// TODO: add code to get the dll and to call the initialize method
                        }
                    }

                }
            }
            catch (Exception /*e*/)
            {
//                MessageBox.Show(e.Message);
            }

            //  Reset all values to 0
            gsensor.pitchAngle.Value = 0.0f;
            gsensor.rollAngle.Value = 0.0f;
            gps.altitude.Value = 0.0f;
            gps.course.Value = 0.0f;
            gps.speed.Value = 0.0f;
        }

        private void DetermineSystemIdleTimeout()
        {
            Object obj = Registry.GetValue("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power", "ScreenPowerOff", 10);
            int updateInterval = 10;
            if (obj == null)
            {
            }
            else
            {
                updateInterval = (int)obj;
            }
            updateInterval /= 2;    //  do it twice per period so we surely don't miss it

            this.timerKeepAlive.Interval = updateInterval * 1000;
            this.timerKeepAlive.Enabled = true;
        }

        private bool SwitchMode(OperationalMode newMode)
        {
            bool bSuccess = true;
            if (newMode == OperationalMode.Simulated)
            {
                //  Terminate the GPS and HTC G Sensor if it was open before
                gsensor.OperationalMode = newMode;
                gps.OperationalMode = newMode;
                compass.OperationalMode = newMode;
                operationalMode = newMode;
                udpProvider.Run();
            }
            else
            {
                udpProvider.Cancel();
                //  Try getting a handle to the HTCSensor.
                //  If it fails, we'll have no G Sensor and so we can not operate in
                //  REAL WORLD mode and we'll have to switch to simulator mode.
                gsensor.OperationalMode = newMode;
                gps.OperationalMode = newMode;
                compass.OperationalMode = newMode;
                operationalMode = newMode;
            }

            return bSuccess;
        }

        unsafe void DrawGLScene()
        {
            float aspectRatio = (float)ClientSize.Width / (float)ClientSize.Height;
            renderer.Clear(true, true);
            renderer.BeginScene();
            renderer.SetViewport(ClientRectangle);
            if (aspectRatio > 1)
            {
                //  landscape mode
                renderer.SetOrthographicProjection(0.0f, 260.0f * aspectRatio, 0.0f, 260.0f, -10.0f, 10.0f);

                gsensor.ScreenOrientation = ScreenOrientation.Landscape;
                sysvars.SetOrientation(true);
            }
            else
            {
                renderer.SetOrthographicProjection(0.0f, 300.0f, 0.0f, 300.0f / aspectRatio, -10.0f, 10.0f);
                gsensor.ScreenOrientation = ScreenOrientation.Portrait;
                sysvars.SetOrientation(false);
            }

            renderer.EnableTexturing(true);

            //  Draw the static panel
            foreach(WorldObject b in blist)
                b.Draw(renderer);
            renderer.EndScene();

            if (bShowFPS)
            {
                renderer.DrawString(Math.Round(myFps, 2).ToString() + " FPS", 0.0f, 285.0f, 0.0f);
            }


            renderer.SwapBuffers();
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //  Intentionally do nothing here
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            DrawGLScene();

            int tickCount = Environment.TickCount;

            int nextFrame = (myLastFrame + 1) % myFrameTracker.Length;
            // elapsed is how long it took to draw 30 frames
            float elapsed = (tickCount - myFrameTracker[nextFrame]) / 1000f;
            float timePerFrame = elapsed / (float)myFrameTracker.Length;
            myFps = 1.0f / timePerFrame;
            myLastFrame = nextFrame;
            myFrameTracker[nextFrame] = tickCount;

            fDelta += elapsed;
            if (fDelta > 0.2f)
            {
                gps.Update(fDelta);
                gsensor.Update(fDelta);
                compass.Update(fDelta);
                fDelta -= 0.2f;

            }

            Invalidate();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            foreach (WorldObject o in blist)
            {
                o.Dispose();
            }
            blist.Clear();
            renderer.Dispose();
            base.OnClosing(e);
        }

        private void TestForm_Resize(object sender, EventArgs e)
        {
            //  Clear the offsets
            gsensor.resetOffset();
        }

        #region Timer Functions
        private void timer1_Tick(object sender, EventArgs e)
        {
            Invalidate();
        }

        private void timerKeepAlive_Tick(object sender, EventArgs e)
        {
            SystemIdleTimerReset();
        }
        #endregion Timer Functions

        #region Menu Function Handlers
        private void myExitMenuItem_Click(object sender, EventArgs e)
        {
            udpProvider.Cancel();
                
            gps.Dispose();
            gsensor.Dispose();
            compass.Dispose();
            Close();
        }

        private void OnMenuModeSimulated(object sender, EventArgs e)
        {
            if (!menuModeSimulated.Checked)
            {
                //  try to change the mode
                if (SwitchMode(OperationalMode.Simulated))
                {
                    //  success, so adopt new setting
                    this.menuModeReal.Checked = false;
                    this.menuModeSimulated.Checked = true;
                }
                else
                {
                    //  failed to switch so keep the current
                }
            }
        }

        private void OnMenuModeReal(object sender, EventArgs e)
        {
            if (!menuModeReal.Checked)
            {
                //  try to change the mode
                if (SwitchMode(OperationalMode.RealWorld))
                {
                    //  success, so adopt new setting
                    this.menuModeReal.Checked = true;
                    this.menuModeSimulated.Checked = false;
                }
                else
                {
                    //  failed to switch so keep the current
                }
            }
        }

        private void OnMenuFilterFloatingAverage(object sender, EventArgs e)
        {
            if (!menuFilterFloatingAverage.Checked)
            {
                menuFilterFloatingAverage.Checked = true;
                menuFilterNone.Checked = false;

                gsensor.Filter = true;
            }
        }

        private void OnMenuFilterNone(object sender, EventArgs e)
        {
            if (!menuFilterNone.Checked)
            {
                menuFilterFloatingAverage.Checked = false;
                menuFilterNone.Checked = true;

                gsensor.Filter = false;
            }
        }

        private void OnMenuDisplayFPS(object sender, EventArgs e)
        {
            if (bShowFPS)
            {
                menuDisplayFrameRate.Checked = false;
                bShowFPS = false;
            }
            else
            {
                menuDisplayFrameRate.Checked = true;
                bShowFPS = true;
            }
        }

        private void OnMenuOffsetReset(object sender, EventArgs e)
        {
            gsensor.resetOffset();
        }

        private void OnMenuOffsetSet(object sender, EventArgs e)
        {
            gsensor.setOffset();
        }

        private void menuModeUseDirectX_Click(object sender, EventArgs e)
        {
            menuModeUseDirectX.Checked = true;
            menuModeUseOpenGL.Checked = false;

            RegistryClass.SetValue("Software\\LSD\\PFD", "UseDirectX", 1);

            MessageBox.Show("Please restart the program.");
        }

        private void menuModeUseOpenGL_Click(object sender, EventArgs e)
        {
            menuModeUseOpenGL.Checked = true;
            menuModeUseDirectX.Checked = false;

            RegistryClass.SetValue("Software\\LSD\\PFD", "UseDirectX", 0);

            MessageBox.Show("Please restart the program.");
        }
        #endregion Menu Function Handlers

        #region IExtensionFactory Members

        public bool RegisterSystemVariable_bool(string szVariableName, ISystemVarConnector<bool> boolVar)
        {
            if (!sysVars.boolVars.ContainsKey(szVariableName))
            {
                sysVars.boolVars[szVariableName] = new List<ISystemVarConnector<bool>>();
            }
            sysVars.boolVars[szVariableName].Add(boolVar);

            return true;
        }
        public bool RegisterSystemVariable_float(string szVariableName, ISystemVarConnector<float> floatVar)
        {
            if (!sysVars.floatVars.ContainsKey(szVariableName))
            {
                sysVars.floatVars[szVariableName] = new List<ISystemVarConnector<float>>();
            }
            sysVars.floatVars[szVariableName].Add(floatVar);

            return true;
        }

        public bool RegisterSystemVariable_boolobject(string szVariableName, ISystemVariable<bool> boolVarObj)
        {
            if (!sysVars.objVarsBool.ContainsKey(szVariableName))
            {
                sysVars.objVarsBool[szVariableName] = boolVarObj;
                return true;
            }
            else
                return false;
        }
        public bool RegisterSystemVariable_floatobject(string szVariableName, ISystemVariable<float> floatVarObj)
        {
            if (!sysVars.objVarsFloat.ContainsKey(szVariableName))
            {
                sysVars.objVarsFloat[szVariableName] = floatVarObj;
                return true;
            }
            else
                return false;
        }

        public IEnumerator<ISystemVarConnector<bool>> GetSystemVariable_bool(string szVariableName)
        {
            return sysVars.boolVars[szVariableName].GetEnumerator();
        }
        public IEnumerator<ISystemVarConnector<float>> GetSystemVariable_float(string szVariableName)
        {
            return sysVars.floatVars[szVariableName].GetEnumerator();
        }

        public ISystemVariable<bool> GetSystemVariable_boolobject(string szVariableName)
        {
            return sysVars.objVarsBool[szVariableName];
        }
        public ISystemVariable<float> GetSystemVariable_floatobject(string szVariableName)
        {
            return sysVars.objVarsFloat[szVariableName];
        }

        public IGlobalPoolContainer GetGlobalPoolContainer()
        {
            return poolContainer;
        }

        #endregion
    }
}